package com.cyw.common.utils;

import org.apache.commons.lang.ArrayUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Optional;

import static java.math.BigDecimal.ZERO;
import static java.math.RoundingMode.HALF_UP;

/**
 * BigDecimal工具类
 */
public class DecimalUtils {

    // 小数后n位
    public static final int DECIMAL_BIT_TWO = 2;
    public static final int DECIMAL_BIT_THREE = 3;
    public static final int DECIMAL_BIT_FOUR = 4;
    public static final int DECIMAL_BIT_FIVE = 5;
    public static final int DECIMAL_BIT_SIX = 6;


    /**
     * 最大值,null值为zero
     *
     * @param nums
     * @return
     */
    public static BigDecimal max(BigDecimal... nums) {
        if (ArrayUtils.isEmpty(nums)) {
            return ZERO;
        }

        BigDecimal max = nums[0];
        for (BigDecimal num : nums) {
            max = compareTo(max, nullValueToZero(num)) >= 0 ? max : num;
        }
        return max;
    }

    /**
     * 最小值,null值为zero
     *
     * @param nums
     * @return
     */
    public static BigDecimal min(BigDecimal... nums) {
        if (ArrayUtils.isEmpty(nums)) {
            return ZERO;
        }

        BigDecimal min = nums[0];
        for (BigDecimal num : nums) {
            min = compareTo(min, nullValueToZero(num)) < 0 ? min : num;
        }
        return min;
    }

    /**
     * 加法, 无舍入，null值为zero
     *
     * @param nums
     * @return
     */
    public static BigDecimal add(BigDecimal... nums) {
        if (ArrayUtils.isEmpty(nums)) {
            return ZERO;
        }

        BigDecimal sum = ZERO;
        for (BigDecimal num : nums) {
            sum = sum.add(nullValueToZero(num));
        }
        return sum;
    }

    /**
     * 加法，四舍五入到指定精度，null值为zero
     *
     * @param scale
     * @param nums
     * @return
     */
    public static BigDecimal add(int scale, BigDecimal... nums) {
        return round(add(nums), scale, HALF_UP);
    }

    /**
     * 加法，指定舍入模式到指定精度，null值为zero
     *
     * @param scale
     * @param nums
     * @return
     */
    public static BigDecimal add(int scale, RoundingMode roundingMode, BigDecimal... nums) {
        return round(add(nums), scale, roundingMode);
    }

    /**
     * 减法，无舍入，null值为zero
     *
     * @param numA
     * @param numB
     * @return
     */
    public static BigDecimal subtract(BigDecimal numA, BigDecimal numB) {
        return nullValueToZero(numA).subtract(nullValueToZero(numB));
    }

    /**
     * 减法，无舍入，null值为zero
     *
     * @param origin
     * @param subtracts
     * @return
     */
    public static BigDecimal subtract(BigDecimal origin, BigDecimal... subtracts) {
        BigDecimal result = origin;
        for (BigDecimal subtract : subtracts) {
            result = subtract(result, subtract);
        }
        return result;
    }

    /**
     * 若A null 返回 0
     * 若B null 返回A
     * 若A < B 返回 0
     * 默认保留两位小数，四舍五入
     *
     * @return 两数之差, 非负数
     */
    public static BigDecimal safeSubtract(BigDecimal numA, BigDecimal numB) {
        return safeSubtract(numA, numB, DECIMAL_BIT_TWO, HALF_UP);
    }

    /**
     * 若A null 返回 0
     * 若B null 返回A
     * 若A < B 返回 0
     * 默认四舍五入
     *
     * @param scale 保留小数位数
     * @return 两数之差, 非负数
     */
    public static BigDecimal safeSubtract(BigDecimal numA, BigDecimal numB, Integer scale) {
        return safeSubtract(numA, numB, scale, HALF_UP);
    }

    /**
     * 若A null 返回 0
     * 若B null 返回A
     * 若A < B 返回 0
     *
     * @param scale        保留小数位数
     * @param roundingMode {@link RoundingMode}
     * @return 两数之差, 非负数
     */
    public static BigDecimal safeSubtract(BigDecimal numA, BigDecimal numB, Integer scale, RoundingMode roundingMode) {
        BigDecimal result = round(nullValueToZero(numA).subtract(nullValueToZero(numB)), scale, roundingMode);
        return result.compareTo(ZERO) > 0 ? result : ZERO;
    }

    /**
     * 乘法, 无舍入，null值为1
     *
     * @param nums
     * @return
     */
    public static BigDecimal multiply(BigDecimal... nums) {
        if (ArrayUtils.isEmpty(nums)) {
            return ZERO;
        }
        BigDecimal result = BigDecimal.ONE;
        for (BigDecimal num : nums) {
            result = result.multiply(nullValueToDefault(num, BigDecimal.ONE));
        }
        return result;
    }

    /**
     * 乘法，四舍五入到指定精度，null值为1
     *
     * @param scale
     * @param nums
     * @return
     */
    public static BigDecimal multiply(int scale, BigDecimal... nums) {
        return round(multiply(nums), scale, HALF_UP);
    }

    /**
     * 乘法,指定舍入模式到指定精度，null值为1
     *
     * @param scale
     * @param roundingMode
     * @param nums
     * @return
     */
    public static BigDecimal multiply(int scale, RoundingMode roundingMode, BigDecimal... nums) {
        return round(multiply(nums), scale, roundingMode);
    }

    /**
     * 除法，四舍五入到指定精度，除数（numB）为null或zero时返回zero
     *
     * @param numA
     * @param numB
     * @param scale
     * @return
     */
    public static BigDecimal divide(BigDecimal numA, BigDecimal numB, int scale) {
        return divide(numA, numB, scale, HALF_UP);
    }

    /**
     * 除法，指定舍入模式到指定精度，除数（numB）为null或zero时返回zero
     *
     * @param numA
     * @param numB
     * @param scale
     * @param roundingMode
     * @return
     */
    public static BigDecimal divide(BigDecimal numA, BigDecimal numB, int scale, RoundingMode roundingMode) {
        if (numB == null || compareTo(numB, ZERO) == 0) {
            return ZERO;
        }
        return nullValueToZero(numA).divide(numB, scale, roundingMode);
    }

    /**
     * 四舍五入到scale精度
     *
     * @param num
     * @param scale
     * @return
     */
    public static BigDecimal round(BigDecimal num, int scale) {
        return num.setScale(scale, HALF_UP);
    }

    /**
     * 指定舍入模式到指定精度
     *
     * @param num
     * @param scale
     * @param mode
     * @return
     */
    public static BigDecimal round(BigDecimal num, int scale, RoundingMode mode) {
        return num.setScale(scale, mode);
    }

    /**
     * 比较两個数字值大小, null值为zero
     *
     * @param numA 数字1
     * @param numB 数字2
     * @return 结果 a小于b -1, a等于b 0, a大于b 1
     */
    public static int compareTo(BigDecimal numA, BigDecimal numB) {
        return nullValueToZero(numA).compareTo(nullValueToZero(numB));
    }

    /**
     * 比较两個数字值大小（按指定精度四舍五入后）, null值为zero
     *
     * @param numA  数字1
     * @param numB  数字2
     * @param scale 数字保留小数位数
     * @return 结果 a小于b -1, a等于b 0, a大于b 1
     */
    public static int compareTo(BigDecimal numA, BigDecimal numB, int scale) {
        return compareTo(round(nullValueToZero(numA), scale, HALF_UP), round(nullValueToZero(numB), scale, HALF_UP));
    }

    /**
     * numA大于numB
     *
     * @param numA
     * @param numB
     * @return
     */
    public static boolean greaterThan(BigDecimal numA, BigDecimal numB) {
        return greaterThan(numA, numB, false);
    }

    /**
     * 是否大于等于另一数字[根据 参数inclusive决定]
     *
     * @param numA      数字
     * @param numB      数字
     * @param inclusive true为大于等于 false为大于，null值为大于
     * @return
     */
    public static boolean greaterThan(BigDecimal numA, BigDecimal numB, Boolean inclusive) {
        if (inclusive == null) {
            inclusive = false;
        }
        return inclusive ?
                compareTo(nullValueToZero(numA), nullValueToZero(numB)) >= 0 :
                compareTo(nullValueToZero(numA), nullValueToZero(numB)) > 0;
    }

    /**
     * 是否小于等于另一数字[根据 参数inclusive决定]
     *
     * @param numA      数字
     * @param numB      数字
     * @param inclusive true为小于等于 false为小于，null值为小于
     * @return
     */
    public static boolean lessThan(BigDecimal numA, BigDecimal numB, Boolean inclusive) {
        if (inclusive == null) {
            inclusive = false;
        }
        return inclusive ?
                compareTo(nullValueToZero(numA), nullValueToZero(numB)) <= 0 :
                compareTo(nullValueToZero(numA), nullValueToZero(numB)) < 0;
    }

    /**
     * 是否小于零
     *
     * @param numA 数字
     * @return
     */
    public static Boolean ltZero(BigDecimal numA) {
        return lessThanZero(numA, false);
    }

    /**
     * 是否小于等于零
     *
     * @param numA 数字
     * @return
     */
    public static Boolean leZero(BigDecimal numA) {
        return lessThanZero(numA, true);
    }

    /**
     * 是否小于等于零[根据 参数inclusive决定]
     *
     * @param numA      数字
     * @param inclusive true为小于等于 false为等于，null值为小于
     * @return
     */
    private static Boolean lessThanZero(BigDecimal numA, Boolean inclusive) {
        return lessThan(numA, ZERO, inclusive);
    }

    /**
     * 是否大于零
     *
     * @param numA 数字
     * @return
     */
    public static Boolean gtZero(BigDecimal numA) {
        return greaterThanZero(numA, false);
    }

    /**
     * 是否大于等于零
     *
     * @param numA 数字
     * @return
     */
    public static Boolean geZero(BigDecimal numA) {
        return greaterThanZero(numA, true);
    }

    /**
     * 是否大于等于零[根据 参数inclusive决定]
     *
     * @param numA      数字
     * @param inclusive true为大于等于 false为等于，null值为大于
     * @return
     */
    private static Boolean greaterThanZero(BigDecimal numA, Boolean inclusive) {
        return greaterThan(numA, ZERO, inclusive);
    }

    /**
     * numA不小于（大于或等于）numbB
     *
     * @param numA
     * @param numB
     * @return
     */
    public static boolean notLessThan(BigDecimal numA, BigDecimal numB) {
        return compareTo(nullValueToZero(numA), nullValueToZero(numB)) >= 0;
    }

    /**
     * numA小于numbB
     *
     * @param numA
     * @param numB
     * @return
     */
    public static boolean lessThan(BigDecimal numA, BigDecimal numB) {
        return compareTo(nullValueToZero(numA), nullValueToZero(numB)) < 0;
    }

    /**
     * numA不大于（小于或等于）numbB
     *
     * @param numA
     * @param numB
     * @return
     */
    public static boolean notGreaterThan(BigDecimal numA, BigDecimal numB) {
        return compareTo(nullValueToZero(numA), nullValueToZero(numB)) <= 0;
    }

    /**
     * numA等于numbB(值相等，精度可以不相等)
     *
     * @param numA
     * @param numB
     * @return
     */
    public static boolean equalTo(BigDecimal numA, BigDecimal numB) {
        return compareTo(nullValueToZero(numA), nullValueToZero(numB)) == 0;
    }

    /**
     * 等于zero
     *
     * @param num
     * @return
     */
    public static boolean equalToZero(BigDecimal num) {
        return equalTo(num, ZERO);
    }

    /**
     * 将num平均分为partNum份，余数放到最后一份中
     *
     * @param num     要被平分的数量
     * @param partNum 平分的份数
     * @return
     */
    public static BigDecimal[] partion(BigDecimal num, int partNum) {
        if (partNum <= 0) {
            return new BigDecimal[]{num};
        }

        BigDecimal[] arr = new BigDecimal[partNum];
        BigDecimal item = divide(num, new BigDecimal(partNum), 2);
        BigDecimal remainder = subtract(num, multiply(item, new BigDecimal(partNum)));
        for (int i = 0; i < partNum; i++) {
            if (i == partNum - 1) {
                arr[i] = add(item, remainder);
            } else {
                arr[i] = item;
            }
        }
        return arr;
    }

    /**
     * 如果value为null返回 zero
     *
     * @param value
     * @return
     */
    public static BigDecimal nullValueToZero(BigDecimal value) {
        return nullValueToDefault(value, ZERO);
    }

    /**
     * 如果value为null返回 defaultValue
     *
     * @param value
     * @param defaultValue
     * @return
     */
    public static BigDecimal nullValueToDefault(BigDecimal value, BigDecimal defaultValue) {
        return Optional.ofNullable(value).orElse(defaultValue);
    }

    /**
     * double转换为精确的BigDecimal
     *
     * @param value
     * @return
     */
    public static BigDecimal toDecimal(double value) {
        return new BigDecimal(Double.toString(value));
    }

    /**
     * float转换为精确的BigDecimal
     *
     * @param value
     * @return
     */
    public static BigDecimal toDecimal(float value) {
        return new BigDecimal(Float.toString(value));
    }

    /**
     * 格式化十进制数字
     * <a href="https://www.jianshu.com/p/b3699d73142e">参考链接1</a>
     * <a href="https://www.baeldung.com/java-decimalformat">参考链接2</a>
     *
     * @param value
     * @param format
     * @return
     */
    public static String decimalFormat(BigDecimal value, String format) {
        return new DecimalFormat(format).format(value);
    }
}
